package com.sinotrans.gateway.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import com.google.gson.Gson;
import com.sinotrans.common.entity.SecurityProperties;
import com.sinotrans.common.exception.BusinessException;
import com.sinotrans.common.security.AuthEntity;
import com.sinotrans.common.security.auth.OAuthUtils;
import com.sinotrans.common.util.Utils;
import com.sinotrans.gateway.entity.JwtEntity;
import com.sinotrans.gateway.entity.RSAPublicKey;
import com.sinotrans.gateway.service.JwtService;
import com.sinotrans.logger.name.AppNameLoader;

@RefreshScope
@Service
public class JwtServiceImpl implements JwtService {
	private static final Logger logger = LoggerFactory.getLogger(JwtServiceImpl.class);
	private String channel = AppNameLoader.appName();

	@Autowired
	protected SecurityProperties securityProperties;

	@Autowired
	protected RestTemplate restTemplate;

	private volatile RSAPublicKey publicKey;

	private Object lock = new Object();

	private Gson gson = Utils.getGson();

	private RSAPublicKey publicKey() {
		RSAPublicKey publicKey = null;
		try {
			String name = securityProperties.getApplication().getName();
			String contextPath = securityProperties.getApplication().getContextPath();
			StringBuilder url = new StringBuilder();
			url.append("http://").append(name);
			if (!StringUtils.isEmpty(contextPath)) {
				url.append(contextPath);
			}
			url.append("/oauth/token_key");
			publicKey = restTemplate.getForObject(url.toString(), RSAPublicKey.class);
		} catch (Exception e) {
			logger.error("RSAPublicKey error.", e);
		}
		return publicKey;
	}

	@Override
	public RSAPublicKey jwtPublicKey() {
		if (publicKey == null || StringUtils.isEmpty(publicKey.getValue())) {
			synchronized (lock) {
				if (publicKey == null || StringUtils.isEmpty(publicKey.getValue())) {
					publicKey = publicKey();
				}
			}
		}
		return publicKey;
	}

	protected JwtEntity decodeVerify(String token) throws Exception {
		RSAPublicKey publicKey = jwtPublicKey();
		Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publicKey.getValue()));
		String claims = jwt.getClaims();
		JwtEntity jwtEntity = gson.fromJson(claims, JwtEntity.class);
		boolean isExp = System.currentTimeMillis() > jwtEntity.getExp() * 1000;
		if (isExp) {
			logger.error("Token has expired");
			throw new BusinessException("Token has expired");
		}
		return jwtEntity;
	}

	protected boolean isRefreshToken(JwtEntity jwtEntity) {
		return !StringUtils.isEmpty(jwtEntity.getAti());
	}

	private AuthEntity jwtToAuth(JwtEntity jwtEntity) {
		AuthEntity authEntity = new AuthEntity();
		authEntity.setChn(channel);
		authEntity.setIsd(OAuthUtils.True);
		authEntity.setRls(jwtEntity.getAuthorities());
		authEntity.setUn(jwtEntity.getUser_name());
		return authEntity;
	}

	@Override
	public AuthEntity decodeVerifyAccesstoken(String accessToken) throws Exception {
		JwtEntity jwtEntity = decodeVerify(accessToken);
		boolean isRefreshToken = isRefreshToken(jwtEntity);
		if (isRefreshToken) {
			logger.error("Token is refresh token");
			throw new BusinessException("Token is refresh token");
		}
		//
		AuthEntity authEntity = jwtToAuth(jwtEntity);
		return authEntity;
	}

	@Override
	public AuthEntity decodeVerifyRefreshToken(String refreshToken) throws Exception {
		JwtEntity jwtEntity = decodeVerify(refreshToken);
		boolean isRefreshToken = isRefreshToken(jwtEntity);
		if (!isRefreshToken) {
			logger.error("Token is not refresh token");
			throw new BusinessException("Token is not refresh token");
		}
		//
		AuthEntity authEntity = jwtToAuth(jwtEntity);
		return authEntity;
	}

}
